package edu.buffalo.cse.cse486_586.simpledynamo;

import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import android.util.Log;

import edu.buffalo.cse.cse486_586.simpledynamo.util.AppUtil;
import edu.buffalo.cse.cse486_586.simpledynamo.util.CommonUtil;

public class DynamoHelper {
	private int localPort = -1;
	private String localHashPort = "";
	private int localRedirPort = -1;
	private Map<String, Integer> routingTable;
	private Map<String, Boolean> flagTable;
	private int counter;

	public DynamoHelper() {
		this.routingTable = new TreeMap<String, Integer>();
		this.flagTable = new TreeMap<String, Boolean>();
		this.counter = 0;
		if (routingTable.size() > 0) {
			routingTable.clear();
			flagTable.clear();
		}

		for (int i = 0; i < AppUtil.CONSOLE_PORT.length; i++) {
			String hashstr = CommonUtil.genHash(String
					.valueOf(AppUtil.CONSOLE_PORT[i]));
			routingTable.put(hashstr, AppUtil.REDIR_PORT[i]);
			flagTable.put(hashstr, true);
		}
	}

	/*
	 * get key-value unique version number
	 */
	synchronized public String genVersionNumber() {
		return String.valueOf(localPort) + String.valueOf(counter++);
	}

	/*
	 * return the preference list for given hashed key
	 * 
	 * @return list of redirct port
	 */
	public List<Integer> getPreferenceList(String key) {
		List<Integer> preferenceList = new LinkedList<Integer>();

		String[] hashArray = new String[routingTable.keySet().size()];
		routingTable.keySet().toArray(hashArray);

		int i = 0;
		int l = hashArray.length;
		while (i < l) {
			if (i > 0) {
				if (key.compareTo(hashArray[i]) <= 0
						&& key.compareTo(hashArray[i - 1]) > 0) {
					for (int j = i; preferenceList.size() < AppUtil.NUM_REPLICA; j = ++j
							% routingTable.size()) {
						preferenceList.add(routingTable.get(hashArray[j]));
					}
					break;
				}
			} else {
				if ((key.compareTo(hashArray[i]) <= 0 && key
						.compareTo("0000000000000000000000000000000000000000") >= 0)
						|| (key.compareTo(hashArray[l - 1]) > 0)) {
					for (int j = i; preferenceList.size() < AppUtil.NUM_REPLICA; j = ++j
							% routingTable.size()) {
						preferenceList.add(routingTable.get(hashArray[j]));
					}
					break;
				}
			}
			i++;
		}

		// check for the aliveness
		for (int z = 0; z < preferenceList.size(); z++) {
			String hashValue = getHasStringByRedirctPort(preferenceList.get(z));
			if (!flagTable.get(hashValue)) {
				preferenceList.remove(z);
			}
		}

		return preferenceList;
	}

	public String getHasStringByRedirctPort(int redPort) {

		for (Map.Entry<String, Integer> entry : routingTable.entrySet()) {
			if (entry.getValue() == redPort)
				return entry.getKey();
		}

		return null;
	}

	/*
	 * send failre message to all peers
	 */
	public void sendFailurePkg(String failurePortHash) {
		String pkg = AppUtil.PACKAGE_FAILUR_REPORT + AppUtil.PACKAGE_DELIMITER
				+ failurePortHash;

		for (Map.Entry<String, Integer> entry : routingTable.entrySet()) {
			if (flagTable.get(entry.getKey())
					&& entry.getValue() != localRedirPort) {
				try {
					ExecutorService executor = Executors.newFixedThreadPool(1);
					Future<Boolean> futrue = executor.submit(new SendingThread(
							AppUtil.EMULATOR_IP, entry.getValue(), pkg, false));
					futrue.get();
				} catch (InterruptedException e) {
					Log.e("DynamoHelper", "Exception", e);
				} catch (ExecutionException e) {
					Log.e("DynamoHelper", "Exception", e);
				}
			}
		}
	}

	public void sendRecoveryPkg() {
		String pkg = AppUtil.PACKAGE_RECOVERY_REPORT
				+ AppUtil.PACKAGE_DELIMITER + localHashPort;

		for (Map.Entry<String, Integer> entry : routingTable.entrySet()) {
			if (flagTable.get(entry.getKey())
					&& entry.getValue() != localRedirPort) {
				try {
					ExecutorService executor = Executors.newFixedThreadPool(1);
					Future<Boolean> futrue = executor.submit(new SendingThread(
							AppUtil.EMULATOR_IP, entry.getValue(), pkg, false));
					futrue.get();
				} catch (InterruptedException e) {
					Log.e("DynamoHelper", "Exception", e);
				} catch (ExecutionException e) {
					Log.e("DynamoHelper", "Exception", e);
				}
			}
		}
	}

	public void broadcastPkg(){
		String pkg = "testing!!!!!!!!!!!!!!!!!!";
		
		for (Map.Entry<String, Integer> entry : routingTable.entrySet()) {
			if (flagTable.get(entry.getKey())
					&& entry.getValue() != localRedirPort) {
				try {
					ExecutorService executor = Executors.newFixedThreadPool(1);
					Future<Boolean> futrue = executor.submit(new SendingThread(
							AppUtil.EMULATOR_IP, entry.getValue(), pkg, false));
					futrue.get();
				} catch (InterruptedException e) {
					Log.e("DynamoHelper", "Exception", e);
				} catch (ExecutionException e) {
					Log.e("DynamoHelper", "Exception", e);
				}
			}
		}
	}
	
	public boolean uniCast(int redirPort, String pkg, boolean isWait){
		boolean isComplete = false;
		try {
			ExecutorService executor = Executors.newFixedThreadPool(1);
			Future<Boolean> futrue = executor.submit(new SendingThread(
					AppUtil.EMULATOR_IP, redirPort, pkg, isWait));
			isComplete = futrue.get();
		} catch (InterruptedException e) {
			Log.e("DHelper", "Exception", e);
		} catch (ExecutionException e) {
			Log.e("DHelper", "Exception", e);
		}

		return isComplete;
	}
	
	/*
	 * when we find one machine back to alive/failure, change the status key is
	 * hashed value
	 */
	public void changeGroupView(String key, boolean b) {
		flagTable.put(key, b);
	}

	/*
	 * get the status of one machine in group key is hashed value
	 */
	public boolean isAlive(String key) {
		return flagTable.containsKey(key) ? flagTable.get(key) : false;
	}
	
	
	/*
	 * recovery message
	 */

	public int getLocalPort() {
		return localPort;
	}

	public void setLocalPort(int localPort) {
		this.localPort = localPort;
	}

	public String getLocalHashPort() {
		return localHashPort;
	}

	public void setLocalHashPort(String localHashPort) {
		this.localHashPort = localHashPort;
	}

	public int getLocalRedirPort() {
		return localRedirPort;
	}

	public void setLocalRedirPort(int localRedirPort) {
		this.localRedirPort = localRedirPort;
	}
}
